package tiltmaze;

import java.util.Arrays;

/**
 * Class representing a maze.
 * @author Juraj Nižnan
 */
public class Maze {

    /**
     * 2D array of cells
     */
    private Cell[][] cells;

    /**
     * number of rows
     */
    private int numOfRows;

    /**
     * number of columns
     */
    private int numOfCols;

    /**
     * starting coordinates of the ball
     */
    private RowCol start;

    /**
     * number of squares
     */
    private int numOfCheckpoints = 0;


    /**
     * constructor, creates an empty maze
     * @param numOfRows number of rows
     * @param numOfCols number of columns
     */
    public Maze(int numOfRows, int numOfCols) {
        this.numOfRows = numOfRows;
        this.numOfCols = numOfCols;
        cells = new Cell[numOfRows][numOfCols];
        for (int i=0; i<numOfRows; i++){
            for (int j=0; j<numOfCols; j++){
                Cell c = new Cell();
                if (i==0) c.buildWall(0);
                if (j==numOfCols-1) c.buildWall(1);
                if (i==numOfRows-1) c.buildWall(2);
                if (j==0) c.buildWall(3);
                cells[i][j] = c;
            }
        }
        cells[0][0].setStart(true);
        start = new RowCol(0, 0);
    }

    /**
     * constructor, creates a maze with cells specified by their numeric codes
     * @param cellCodes
     */
    public Maze(int[][] cellCodes){
        numOfRows = cellCodes.length;
        numOfCols = cellCodes[0].length;
        cells = new Cell[numOfRows][numOfCols];
        numOfCheckpoints = 0;
        for (int i=0; i<numOfRows; i++){
            for (int j=0; j<numOfCols; j++){
                Cell c = new Cell(cellCodes[i][j]);
                if (c.getStart()) {
                    start = new RowCol(i, j);
                }
                numOfCheckpoints += (c.getCheckpoint()) ? 1 : 0;
                cells[i][j] = c;
            }
        }
    }

    public Maze(Cell[][] cells){
        numOfRows = cells.length;
        numOfCols = cells[0].length;
        this.cells = cells;
        numOfCheckpoints = 0;
        for (int i=0; i<numOfRows; i++){
            for (int j=0; j<numOfCols; j++){
                Cell c = cells[i][j];
                if (c.getStart()) {
                    start = new RowCol(i, j);
                }
                numOfCheckpoints += (c.getCheckpoint()) ? 1 : 0;
            }
        }
    }

    /**
     * @return an exact copy of this maze
     */
    public Maze copy(){
        return new Maze(this.getCellCodes());
    }

    /**
     * @return copy of cells (i.e. modifying them will not modify the maze)
     */
    public Cell[][] getCells() {
        Cell[][] ret = new Cell[numOfRows][numOfCols];
        for (int i=0; i<numOfRows; i++){
            for (int j=0; j<numOfCols; j++) {
                ret[i][j] = cells[i][j].copy();
            }
        }
        return ret;
    }

    /**
     * @return 2D array of numeric codes of the cells of the maze
     */
    public int[][] getCellCodes() {
        int[][] ret = new int[numOfRows][numOfCols];
        for (int i=0; i<numOfRows; i++) {
            for (int j=0; j<numOfCols; j++) {
                ret[i][j]=cells[i][j].getCode();
            }
        }
        return ret;
    }

    /**
     * @return number of columns
     */
    public int getNumOfCols() {
        return numOfCols;
    }

    /**
     * @return number of rows
     */
    public int getNumOfRows() {
        return numOfRows;
    }

    /**
     * @return number of squares
     */
    public int getNumOfCheckpoints() {
        return numOfCheckpoints;
    }

    /**
     * @return coordinates of the starting position of the ball
     */
    public RowCol getStart() {
        return new RowCol(start);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final Maze other = (Maze) obj;
        if (!Arrays.deepEquals(this.cells, other.cells)) {
            return false;
        }
        if (this.numOfRows != other.numOfRows) {
            return false;
        }
        if (this.numOfCols != other.numOfCols) {
            return false;
        }
        if (this.start != other.start && (this.start == null || !this.start.equals(other.start))) {
            return false;
        }
        if (this.numOfCheckpoints != other.numOfCheckpoints) {
            return false;
        }
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        hash = 43 * hash + Arrays.deepHashCode(this.cells);
        hash = 43 * hash + this.numOfRows;
        hash = 43 * hash + this.numOfCols;
        hash = 43 * hash + (this.start != null ? this.start.hashCode() : 0);
        hash = 43 * hash + this.numOfCheckpoints;
        return hash;
    }

    @Override
    public String toString() {
        String ret = numOfRows + "," + numOfCols + "\n";
        for (int i = 0; i < numOfRows; i++) {
            for (int j = 0; j < numOfCols; j++) {
                ret += cells[i][j].getCode();
                if (j + 1 < numOfCols) {
                    ret += ",";
                }
            }
            ret += "\n";
        }
        return ret;
    }

    //TODO
    //following methods are currently used only by VIEW package
    //need checking

    public void setCheckpoint(int row, int col, boolean val) {
        if (cells[row][col].getCheckpoint()) {
            if (!val) {
                numOfCheckpoints--;
            }
        } else {
            if (val) {
                numOfCheckpoints++;
            }
        }
        cells[row][col].setCheckpoint(val);
    }

    public void setVerticalWall(int row, int col, boolean val) {
        if (col <numOfCols) {
            boolean[] walls = cells[row][col].getWalls();
            walls[3] = val;
            cells[row][col].setWalls(walls);
        }
        if (col>0) {
            boolean[] walls = cells[row][col-1].getWalls();
            walls[1] = val;
            cells[row][col-1].setWalls(walls);
        }
    }

    public void setHorizontalWall(int row, int col, boolean val) {
        if (row <numOfRows) {
            boolean[] walls = cells[row][col].getWalls();
            walls[0] = val;
            cells[row][col].setWalls(walls);
        }
        if (row>0) {
            boolean[] walls = cells[row-1][col].getWalls();
            walls[2] = val;
            cells[row-1][col].setWalls(walls);
        }
    }

    public void setStart(int row, int col) {
        if (start.row != row || start.col != col) {
            cells[start.row][start.col].setStart(false);
            start = new RowCol(row, col);
            cells[start.row][start.col].setStart(true);
        }
    }


}
